using System;
using System.Text;
using CI.Interfaces;
using System.Runtime.Serialization;

namespace CI.Organism
{
    /// <summary>Step function neuron</summary>
    [DataContract]
    public class StepNeuron : Neuron
    {

        /// <summary>
        /// Constructor with threshold activation value        
        /// </summary>
        /// <param name="thresholdValue">Value where activation takes place</param>
        /// <param name="neuralSystem">Neurla system</param>
        /// <param name="guid">Step Nuerons GUID</param>
        public StepNeuron(long thresholdValue, INeuralSystem neuralSystem, Guid guid)
            : base(thresholdValue, neuralSystem, guid) { }

        /// <summary>
        /// A step neuron with a reduced constructor to be compatible with mutation
        /// </summary>
        /// <param name="organism">Organism it belongs to</param>
        /// <param name="guid">guid</param>
        public StepNeuron(IOrganism organism, Guid guid) : this(0, organism.NeuralSystem, guid) { }

        /// <summary>
        /// Activates the neuron
        /// It calculates the sum and bias of the input weights versus the activation thresold and updates the outputs
        /// </summary>
        /// <returns></returns>
        public override bool ActivationFunction(UInt32 neuralSystemHB)
        {
            var result = false;

            // calculate the sum of the input synapes
            SummedInputValue = SumInputSynapses(neuralSystemHB);

            // compare the input to the threshold
            if (SummedInputValue >= ThresholdValue)
            {
                // fire!
                ActivationEventNotifier(SummedInputValue);

                // if above set result to true
                result = true;
            }

            // notify the learning rules
            UpdateLearningRules(result);

            RaiseActivationFunctionComplete();
            return result;
        }

        private void UpdateLearningRules(bool result)
        {
            foreach (ISynapse synapse in InputSynapse)
            {
                // if they have a learning rule
                if (synapse.LearningRule != null)
                {
                    _simEnv.TraceLogger.LogDebug(string.Format("Learnig Rule : {0} - Target neuron notifited of firing {1}", synapse.LearningRule.GetType().Name, result));
                    synapse.LearningRule.TargetNeuronFired(result);
                }
            }
        }

        private long SumInputSynapses(uint neuralSystemHB)
        {
            long inputSum = 0;

            foreach (ISynapse synape in InputSynapse)
            {
                // evalute the synapse if it has not been done this heartbeat                
                synape.Evaluate(neuralSystemHB);

                try
                {
                    // add the result to the input
                    inputSum += synape.OutputValue;
                }
                // if we are going to create a overflow
                catch (ArithmeticException ex)
                {
                    // if its going to be a positive overflow
                    if ((inputSum > 0) && (synape.OutputValue > 0))
                        inputSum = long.MaxValue;

                    // if its going to be a negative overflow
                    if ((inputSum < 0) && (synape.OutputValue < 0))
                        inputSum = long.MinValue;

                    _simEnv.TraceLogger.LogDebug("Acceptable Exception calculating input sum of neuron" + ex.Message);
#if !DEBUG
                    Console.WriteLine("Exception calculating input sum of neuron", ex);
#endif
                }
            }
            return inputSum;
        }

        /// <summary>
        /// Fire the synapse, notify it that a potential has arrived and the value
        /// </summary>
        public override void FireInput(long inputValue)
        {
            NeuralSystem.AddToWaitingList(this);
        }


        /// <summary>
        /// To String override
        /// </summary>
        /// <returns></returns>
        public override string ToString(string value)
        {
            var sb = new StringBuilder();

            sb.Append("StepNeuron" + Environment.NewLine);
            sb.Append(base.ToString());

            return sb.ToString();
        }

        /// <summary>
        /// basic to string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Step Neuron";
        }
    }

}
